<!DOCTYPE html>
<html class="no-js">
<!--
Copyright 2014 Plan-A Software Ltd. All Rights Reserved.

Use of this source code is governed by the Apache License, Version 2.0.
-->
<!--
  @author kiran@plan-a-software.co.uk (Kiran Lakhotia)
-->

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <title>Typeahead Test - Plan-A Software Ltd.</title>
  <meta name="description" content="Unit tests for the autocomplete component">
  <meta name="viewport" content="width=device-width">
  <link rel="icon" type="image/png" href="../../../../favicon.png">
  <!-- include the relevant style sheets from google closure -->
  <link rel="stylesheet" href="../../../css/autocomplete.css">
</head>

<body>

  <div id="container">
  </div>

  <!-- set the path to goog base -->
  <script src="../../vendor/goog/base.js"></script>
  <script type="text/javascript">
  goog.require('goog.Uri');
  goog.require('goog.dom');
  goog.require('goog.events.KeyCodes');
  goog.require('goog.net.WrapperXmlHttpFactory');
  goog.require('goog.testing.AsyncTestCase');
  goog.require('goog.testing.events');
  goog.require('goog.testing.jsunit');
  goog.require('goog.testing.net.XhrIo');
  goog.require('goog.ui.Component');
  </script>

  <script type="text/javascript" src="remoteobjectmatcher.js"></script>
  <script type="text/javascript" src="autocompleterenderer.js"></script>
  <script type="text/javascript" src="cachingobjectmatcher.js"></script>
  <script type="text/javascript" src="inputhandler.js"></script>
  <script type="text/javascript" src="autocomplete.js"></script>

  <script type="text/javascript">
  var mockServerObjects = [{
    id: 1,
    caption: 'hello'
  }, {
    id: 2,
    caption: 'world'
  }];
  var mockServerStrings = [
    'coding',
    'on',
    'keep',
    'calm',
    'and',
    'carry'
  ];

  function MockXmlHttp() {
    /**
     * The headers for this XmlHttpRequest.
     * @type {!Object.<string>}
     */
    this.headers = {};

    /**
     * The request data string
     * @type {string}
     */
    this.content;
  };

  var lastUri;
  var sendDelay = 0;
  var timerId = null;
  var sendInvalidResponse = false;
  MockXmlHttp.prototype.readyState = goog.net.XmlHttp.ReadyState.UNINITIALIZED;

  MockXmlHttp.prototype.status = 200;

  MockXmlHttp.syncSend = true;

  MockXmlHttp.prototype.send = function(opt_data) {
    this.content = opt_data;
    this.readyState = goog.net.XmlHttp.ReadyState.UNINITIALIZED;

    lastMockXmlHttp.responseText = this.getResponseJson();

    if (MockXmlHttp.syncSend) {
      this.complete();
    }

  };


  MockXmlHttp.prototype.complete = function() {
    this.readyState = goog.net.XmlHttp.ReadyState.LOADING;
    this.onreadystatechange();

    this.readyState = goog.net.XmlHttp.ReadyState.LOADED;
    this.onreadystatechange();

    this.readyState = goog.net.XmlHttp.ReadyState.INTERACTIVE;
    this.onreadystatechange();

    if (timerId != null) {
      window.clearTimeout(timerId);
    }
    var self = this;
    timerId = window.setTimeout(function() {
      self.readyState = goog.net.XmlHttp.ReadyState.COMPLETE;
      self.onreadystatechange();
      self = null;
      timerId = null;
    }, sendDelay);
  };


  MockXmlHttp.prototype.open = function(verb, uri, async) {
    lastUri = uri;
  };

  MockXmlHttp.prototype.abort = function() {
    if(timerId) {
      window.clearTimeout(timerId);
      timerId = null;
    }
  };

  MockXmlHttp.prototype.setRequestHeader = function(key, value) {
    this.headers[key] = value;
  };

  MockXmlHttp.prototype.getResponseJson = function(opt_xssiPrefix) {
    //var uri = new goog.Uri(lastUri);
    var query = new goog.Uri.QueryData(this.content);
    var filterStrings = query.get('token').split(',');
    var useMultiple = query.get('multi') == 1;
    if (!useMultiple) {
      var str = filterStrings.shift();
      if (str)
        filterStrings = [str];
      else
        filterStrings = [];
    }
    var matches = [];
    for (var i = 0, str; str = filterStrings[i]; ++i) {
      str = goog.string.trim(str);
      for (var o = 0, obj; obj = mockServerObjects[o]; ++o) {
        if (goog.string.contains(obj.caption, str) ||
          goog.string.caseInsensitiveCompare(obj.caption, str) == 0) {
          matches.push(obj);
        }
      }
      for (var s = 0, sobj; sobj = mockServerStrings[s]; ++s) {
        if (goog.string.contains(sobj, str) ||
          goog.string.caseInsensitiveCompare(sobj, str) == 0) {
          matches.push(sobj);
        }
      }
    }
    if (sendInvalidResponse)
      return null;
    return goog.json.serialize(matches);
  };

  var lastMockXmlHttp;
  goog.net.XmlHttp.setGlobalFactory(new goog.net.WrapperXmlHttpFactory(
    function() {
      lastMockXmlHttp = new MockXmlHttp();
      return lastMockXmlHttp;
    },
    function() {
      return {};
    }));
  </script>

  <script type="text/javascript">
  var testCase;
  var asyncCallbacks = [];
  var asyncTimer = null;
  var autocomplete = null;
  var inputId = 'myinput';
  var loadingImg = goog.dom.createDom('img', {
    'src': '/alternative/img/ajax-loader.gif'
  });

  function createAutoComplete(multi) {
    autocomplete = new plana.ui.ac.AutoComplete(new goog.Uri('fuchs'), multi, null, inputId);
    autocomplete.setLoadingContent(loadingImg);
    autocomplete.render(document.getElementById('container'));
  }

  var callAsync = function() {
    var wait = function() {
      asyncTimer = null;
      if (lastMockXmlHttp &&
        lastMockXmlHttp.readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
        testCase.continueTesting();
        var next = asyncCallbacks.shift();
        if (next)
          next();
      } else {
        asyncTimer = window.setTimeout(wait, 100);
      }
    };
    testCase.waitForAsync('waiting for aync');
    wait();
  };

  function setToken(token, full) {
    autocomplete.autoComplete.setToken(token, full);
    var input = document.getElementById(inputId);
    input.value = goog.isDefAndNotNull(full)? full:token;
  }
  </script>

  <script type="text/javascript">
  testCase = goog.testing.AsyncTestCase.createAndInstall('autocomplete');

  function setUp() {
    sendDelay = 0;
    sendInvalidResponse = false;
    MockXmlHttp.prototype.status = 200;
  }

  function tearDown() {
    if (timerId != null) {
      window.clearTimeout(timerId);
      timerId = null;
    }
    if (asyncTimer != null) {
      window.clearTimeout(asyncTimer);
      asyncTimer = null;
    }
    asyncCallbacks.length = 0;
    if (autocomplete != null)
      autocomplete.dispose();
    autocomplete = null;
  }

  
  function testCommaInputOnNonMultiple() {
    createAutoComplete(false);
    var checkServerToken = function() {
      var content = decodeURIComponent(lastMockXmlHttp.content);
      assertTrue('incorrect token', goog.string.contains(content, 'token=hello,world'));
    };
    asyncCallbacks.push(checkServerToken);
    setToken('hello,world');
    callAsync();
  }

  function testServerErrorWithNoCacheMatch() {
    MockXmlHttp.prototype.status = 500;
    createAutoComplete(false);
    var checkNoSuggestions = function() {
      var renderer = autocomplete.getAutoComplete().getRenderer();
      assertFalse('should not be showing anything upon server error', renderer.isVisible());
    };
    asyncCallbacks.push(checkNoSuggestions);
    setToken('x');
    callAsync();
  }

  function testServerErrorWithCacheMatch() {
    createAutoComplete(false);
    var waitForCache = function() {
      var renderer = autocomplete.getAutoComplete().getRenderer();
      assertTrue('should be showing hello matches', renderer.isVisible());
      assertEquals('showing only one match', 1, autocomplete.getAutoComplete().getRowCount());

      MockXmlHttp.prototype.status = 500;
      setToken('h');
      callAsync();
    };
    var checkCachedSuggestions = function() {
      var renderer = autocomplete.getAutoComplete().getRenderer();
      assertTrue('should still be showing cached hello matches', renderer.isVisible());
      assertEquals('showing be showing cached match', 1, autocomplete.getAutoComplete().getRowCount());
    };
    asyncCallbacks.push(waitForCache);
    asyncCallbacks.push(checkCachedSuggestions);
    setToken('hel');
    callAsync();
  }

  function testLoadingNotification() {
    createAutoComplete(false);
    sendDelay = 100;
    var _id;
    var loadingChecked = false;
    _id = window.setTimeout(function() {
      var renderer = autocomplete.getAutoComplete().getRenderer();
      assertTrue('should be showing loading message', renderer.isVisible());
      loadingChecked = true;
      _id = null;
    }, 20);
    var checkLoadingFlag = function() {
      if (_id)
        window.clearTimeout(_id);
      assertTrue('did not show loading message', loadingChecked);
    };
    asyncCallbacks.push(checkLoadingFlag);
    setToken('hel');
    callAsync();
  }

  function testNoMatchNotification() {
    createAutoComplete(false);
    var checkNoMatch = function() {
      var renderer = autocomplete.getAutoComplete().getRenderer();
      var children = goog.dom.getChildren(renderer.getElement());
      assertEquals('showing no match message', 1, children.length);
      assertEquals('showing no matches', 0, autocomplete.getAutoComplete().getRowCount());
    };
    asyncCallbacks.push(checkNoMatch);
    setToken('x');
    callAsync();
  }
  
  function testSetModelWithMultiInput() {
    createAutoComplete(true);
    var checkModel = function() {
      assertEquals('showing wrong num matches', 1, autocomplete.getAutoComplete().getRowCount());
      var model = autocomplete.getModel();
      assertEquals('model should be empty array', 0, model.length);
      var nonmatches = autocomplete.getNonMatches();
      assertEquals('should have 2 non matches', 2, nonmatches.length);
      assertArrayEquals('non match content', ['hello', 'w'], nonmatches);
      //select suggestion
      var input = document.getElementById(inputId);
      goog.testing.events.fireKeySequence(input, goog.events.KeyCodes.ENTER);
      model = autocomplete.getModel();
      assertNotNull('model should now exist', model);
      assertEquals('model should have 1 item', 1, model.length);
      assertEquals('model id should be 2', 2, model[0].id);
      assertEquals('model caption should be world', 'world', model[0].caption);
    };
    asyncCallbacks.push(checkModel);
    setToken('world', 'hello,w');
    callAsync();
  }

  function testSetModel() {
    autocomplete = new plana.ui.ac.AutoComplete(new goog.Uri('fuchs'), false, null, inputId);
    autocomplete.setLoadingContent(loadingImg);
    autocomplete.setModel('x games');
    autocomplete.render(document.getElementById('container'));
    var input = autocomplete.getInputHandler().getInput();
    assertEquals('input init didnt work', 'x games', input.value);

    autocomplete.dispose();

    //now try with multi input
    autocomplete = new plana.ui.ac.AutoComplete(new goog.Uri('fuchs'), true, null, inputId);
    autocomplete.setLoadingContent(loadingImg);
    autocomplete.setModel('x games');
    autocomplete.render(document.getElementById('container'));
    var input = autocomplete.getInputHandler().getInput();
    assertEquals('input init didnt work', 'x games, ', input.value);
  }

  function testGetModel() {
    autocomplete = new plana.ui.ac.AutoComplete(new goog.Uri('fuchs'), true, null, inputId);
    autocomplete.render(document.getElementById('container'));

    var inputHandler = autocomplete.getInputHandler();
    inputHandler.setCaseInsensitive(true);
    var input = inputHandler.getInput();
    
    var obj = new plana.ui.ac.RemoteObject({id:1, caption:'Ruff'});
    inputHandler.selectRow(obj);
    
    var str = input.value;
    str += 'Hello, ruff';
    input.value = str;
    goog.testing.events.fireKeySequence(input, goog.events.KeyCodes.U);
    assertEquals('model count', 1, autocomplete.getModel().length);
    assertEquals('model not updated', 1, autocomplete.getModel()[0].id);
    assertEquals('non match count', 1, autocomplete.getNonMatches().length);
    assertEquals('non match error', 'Hello', autocomplete.getNonMatches()[0]);

    inputHandler.setCaseInsensitive(false);
    assertEquals('model count 2', 1, autocomplete.getModel().length);
    assertEquals('model not updated 2', 1, autocomplete.getModel()[0].id);
    assertEquals('non match count 2', 2, autocomplete.getNonMatches().length);
    assertEquals('non match error 2', 'Hello', autocomplete.getNonMatches()[0]);
    assertEquals('non match error 2', 'ruff', autocomplete.getNonMatches()[1]);
  }
  </script>
</body>

</html>